﻿namespace WFBot.Rtc
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Linq;
    using System.Net;
    using System.Threading;
    using Microsoft.Rtc.Collaboration;
    using Microsoft.Rtc.Signaling;
    using WFBot.Framework.Repositories;

    public class RtcCommunicationService : ICommunicationService
    {
        private CollaborationPlatform _collaborationPlatform;
        private UserEndpoint _userEndpoint;
        private List<InstantMessagingCall> _calls = new List<InstantMessagingCall>();
        private List<RtcMessage> _messages = new List<RtcMessage>();
        private readonly Dictionary<string, string> _mapping;

        private AutoResetEvent autoReset = new AutoResetEvent(false);

        public Dictionary<string, string> Mapping
        {
            get
            {
                return _mapping;
            }
        }

        public RtcCommunicationService()
        {
            _mapping = new Dictionary<string, string>();
        }

        public void Initialize()
        {
            InitializePlatform("commflowcoordinator");

            string userName = ConfigurationManager.AppSettings["userName"];
            string password = ConfigurationManager.AppSettings["password"];
            string domain = ConfigurationManager.AppSettings["domain"];
            string uri = ConfigurationManager.AppSettings["uri"];
            string server = ConfigurationManager.AppSettings["server"];

            InitializeUserEndPoint(userName, password, domain, uri, server);
        }

        private void InitializePlatform(string applicationName)
        {
            ClientPlatformSettings clientPlatformSettings = new ClientPlatformSettings(applicationName, SipTransportType.Tls);
            _collaborationPlatform = new CollaborationPlatform(clientPlatformSettings);
            _collaborationPlatform.BeginStartup(EndPlatformStartup, null);

            autoReset.WaitOne();
        }

        private void InitializeUserEndPoint(string userName, string password, string domain, string uri, string server)
        {
            NetworkCredential credential = new NetworkCredential(userName, password, domain);
            UserEndpointSettings userEndpointSettings = new UserEndpointSettings(uri, server);
            userEndpointSettings.Credential = credential;
            _userEndpoint = new UserEndpoint(_collaborationPlatform, userEndpointSettings);
            _userEndpoint.BeginEstablish(EndEndpointEstablish, _userEndpoint);

            autoReset.WaitOne();
        }

        public InstantMessagingCall Call(string channel)
        {
            ConversationSettings convSettings = new ConversationSettings();
            convSettings.Priority = ConversationPriority.Normal;
            convSettings.Subject = "Call from new visitor.";

            Conversation conversation = new Conversation(_userEndpoint, convSettings);
            var call = new InstantMessagingCall(conversation);
            call.ApplicationContext = channel;
            _calls.Add(call);
            call.StateChanged += new EventHandler<CallStateChangedEventArgs>(OnCallStateChanged);
            call.InstantMessagingFlowConfigurationRequested += new EventHandler<InstantMessagingFlowConfigurationRequestedEventArgs>(OnInstantMessagingFlowConfigurationRequested);
            call.BeginEstablish("sip:" + channel.Split(';')[1], null, EndCallEstablish, call);

            autoReset.WaitOne();

            return call;
        }

        public void SendMessage(string channel, string message)
        {
            var call = _calls.FirstOrDefault(c => (string)c.ApplicationContext == channel);

            if (call == null)
            {
                call = Call(channel);
            }

            if (call.State != CallState.Terminated)
                call.Flow.BeginSendMessage(message, EndSendMessage, call.Flow);
            else
            {
                _calls.Remove(call);
                call = Call(channel);
                call.Flow.BeginSendMessage(message, EndSendMessage, call.Flow);
            }
        }

        public void Shutdown()
        {
            _collaborationPlatform.BeginShutdown(EndPlatformShutdown, null);
            autoReset.WaitOne();
        }

        #region Async Callbacks
        private void EndPlatformStartup(IAsyncResult ar)
        {
            try
            {
                _collaborationPlatform.EndStartup(ar);
            }
            catch (ConnectionFailureException connFailEx)
            {
                Console.WriteLine(connFailEx.ToString());
            }
            finally
            {
                autoReset.Set();
            }
        }

        private void EndPlatformShutdown(IAsyncResult ar)
        {
            try
            {
                _collaborationPlatform.EndShutdown(ar);
            }
            finally
            {
                autoReset.Set();
            }
        }

        private void EndEndpointEstablish(IAsyncResult ar)
        {
            try
            {
                _userEndpoint.EndEstablish(ar);
                Console.WriteLine("The User Endpoint owned by URI: ");
                Console.WriteLine(_userEndpoint.OwnerUri);
                Console.WriteLine(" is now established and registered.");

                _userEndpoint.RegisterForIncomingCall<InstantMessagingCall>(OnInstantMessagingCallReceived);
            }
            catch (ConnectionFailureException connFailEx)
            {
                Console.WriteLine(connFailEx.ToString());
            }
            catch (InvalidOperationException iOpEx)
            {
                Console.WriteLine(iOpEx.ToString());
            }
            catch (RegisterException regEx)
            {
                Console.WriteLine(regEx.ToString());
            }
            finally
            {
                autoReset.Set();
            }
        }

        private void EndAcceptCall(IAsyncResult ar)
        {
            InstantMessagingCall instantMessagingCall = ar.AsyncState as InstantMessagingCall;
            try
            {
                instantMessagingCall.EndAccept(ar);
                instantMessagingCall.Flow.MessageReceived += new EventHandler<InstantMessageReceivedEventArgs>(OnInstantMessageReceived);
            }
            catch (RealTimeException exception)
            {
                Console.WriteLine(exception.ToString());
            }
            finally
            {
            }
        }

        private void EndCallEstablish(IAsyncResult result)
        {
            InstantMessagingCall instantMessagingCall = result.AsyncState as InstantMessagingCall;
            Exception ex = null;
            try
            {
                instantMessagingCall.EndEstablish(result);
                autoReset.Set();
                Console.WriteLine("The call is now in the established state.");
            }
            catch (OperationFailureException opFailEx)
            {
                ex = opFailEx;
            }
            catch (RealTimeException rte)
            {
                ex = rte;
            }
            finally
            {
                if (ex != null)
                {
                    Console.WriteLine(ex.ToString());
                    Console.WriteLine("shutting down platform due to error");
                    this.Shutdown();
                }
            }
        }

        private void EndSendMessage(IAsyncResult result)
        {
            InstantMessagingFlow instantMessagingFlow = result.AsyncState as InstantMessagingFlow;
            Exception ex = null;
            try
            {
                instantMessagingFlow.EndSendMessage(result);
                Console.WriteLine("The message has been sent.");
            }
            catch (OperationTimeoutException opTimeEx)
            {
                ex = opTimeEx;
            }
            catch (RealTimeException rte)
            {
                ex = rte;
            }
            finally
            {
                if (ex != null)
                {
                    Console.WriteLine(ex.ToString());
                    Console.WriteLine("shutting down platform due to error");
                    this.Shutdown();
                }
            }
        }

        #endregion

        #region Event Handlers
        // Not available in web scenario
        private void OnInstantMessagingCallReceived(object sender, CallReceivedEventArgs<InstantMessagingCall> e)
        {
            var call = e.Call;
            Console.WriteLine("Call Received! From: " + e.RemoteParticipant.Uri + " Toast is: " + e.ToastMessage);
            _calls.Add(call);
            call.BeginAccept(EndAcceptCall, call);
        }

        private void OnInstantMessageReceived(object sender, InstantMessageReceivedEventArgs e)
        {
            var callId = e.MessageData.SignalingHeaders.FirstOrDefault(h => h.Name == "CALL-ID").GetValue();

            // Find the call associated with the message sender
            var call = _calls.First(c => c.CallId == callId);

            // Push message
            var sessionId = ((string)call.ApplicationContext).Split(';')[0]; // HACK: Daha iyi bir yol bul
            var message = new RtcMessage
            {
                Channel = (string)call.ApplicationContext,
                Sender = _mapping[sessionId],
                Type = "textmessage",
                Body = e.TextBody
            };
            _messages.Add(message);

            Console.WriteLine(e.Sender.Uri + " said: " + e.TextBody);
        }

        private void OnCallStateChanged(object sender, CallStateChangedEventArgs e)
        {
        }

        private void OnInstantMessagingFlowConfigurationRequested(object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
        {
            e.Flow.MessageReceived += new EventHandler<InstantMessageReceivedEventArgs>(OnInstantMessageReceived);
        }

        #endregion

        public void Push(RtcMessage message)
        {
            SendMessage(message.Channel, message.Body);
        }

        public List<RtcMessage> Pull(string channel)
        {
            var messages = _messages.Where(m => m.Channel == channel).ToList();
            _messages.RemoveAll(m => m.Channel == channel);
            return messages;
        }
    }
}
